舵机 ID 设置、校准与遥操作
4.1 确认端口
从臂、主臂各插一个 USB,先查清谁是谁。
lerobot-find-port4.2 分配舵机 ID
每次只把一个舵机接到驱动板上,按提示设成 1~6;从臂用 --robot.,主臂用 --teleop.。
# 从臂
lerobot-setup-motors \
--robot.type=so101_follower \
--robot.port=/dev/tty.usbmodem5B8E1136841# 主臂
lerobot-setup-motors \
--teleop.type=so101_leader \
--teleop.port=/dev/tty.usbmodem5B7901881614.3 校准两臂
生成缺失的那个 .json 校准文件;从臂用 --robot.,主臂用 --teleop.。
# 从臂
lerobot-calibrate \
--robot.type=so101_follower \
--robot.port=/dev/tty.usbmodem5B8E1136841 \
--robot.id=my_awesome_follower_arm# 主臂
lerobot-calibrate \
--teleop.type=so101_leader \
--teleop.port=/dev/tty.usbmodem5B790188161 \
--teleop.id=my_awesome_leader_arm校准不是自动跑完的,它要你手动把机械臂摆两轮。以从臂为例,运行上面的校准命令后,终端会依次给出提示:
- 先摆中位。把每个关节都推到它活动范围的正中间,然后按回车。这一步在记录每个关节的零点。
- 再走满行程。终端提示
Move all joints sequentially through their entire ranges of motion. Recording positions. Press ENTER to stop...,此时手动把每个关节从一端推到另一端,尽量走满。 - **按回车结束。**程序把行程写入校准文件,屏幕上会实时滚动每个关节的
MIN / POS / MAX,方便你确认有没有哪个关节没动到位。
Move my_awesome_follower_arm to the middle of its range of motion and press ENTER....
Move all joints sequentially through their entire ranges of motion.
Recording positions. Press ENTER to stop...
NAME | MIN | POS | MAX
shoulder_pan | 1284 | 2048 | 2812
...
Gripper range adjusted: original min=... -> adjusted min=... (offset=...)校准日志里的 Gripper range adjusted…、以及收不到 5 号舵机信号,都是正常现象,不必处理;若某个关节 MIN 和 MAX 相等,说明那一轮没推动它,需要重来。
4.4 重新校准
- 在已有的校准上重跑:命令执行后会问
Press ENTER to use provided calibration file…, or type 'c' and press ENTER to run calibration,输入c再回车就是重新校准。 - 或者直接删掉缓存里的校准文件,让它当作新设备重跑:
~/.cache/huggingface/lerobot/calibration/下对应的robots/so101_follower/与teleoperators/so101_leader/目录。
4.5 校准文件存在哪
校准的结果就是一个 JSON 文件**,文件名就是那步用的 id**。不指定路径时落在 HuggingFace 缓存里:
~/.cache/huggingface/lerobot/calibration/
├── robots/so101_follower/my_awesome_follower_arm.json
└── teleoperators/so101_leader/my_awesome_leader_arm.json想把它固定在项目里(方便备份、换电脑直接带走),就加 --robot.calibration_dir / --teleop.calibration_dir:
lerobot-calibrate \
--robot.type=so101_follower \
--robot.port=/dev/tty.usbmodem5B8E1136841 \
--robot.id=my_awesome_follower_arm \
--robot.calibration_dir=./data/calibration三个必须配套的点:
calibration_dir给的是目录本身,不是文件路径;文件名由id决定,所以两个臂可以共用一个目录,靠id区分。- **同一个
calibration_dir+ 同一个id必须在校准、遥操作、录制、评测里一路保持一致。**某一处漏写或写错,程序会去默认目录找,找不到就认为「这台臂还没校准过」,于是又走一遍校准流程——看起来像"开机就要求重新校准"。- 想整体改默认位置的根目录,用环境变量
HF_LEROBOT_CALIBRATION,比每条命令都带参数省事。
一条省事的经验:校准是针对这一台机械臂的,把
.json备份下来,重装系统、换电脑、甚至重装 LeRobot 之后拷回去就能直接用,不必重做校准。相当于给机械臂留了一份"出厂身份证",建议连同id一起记在笔记里。
4.6 校准 JSON 里每个字段是什么意思
打开那个 .json 会看到这样的结构,键名就是关节名(和 J1–J6 一一对应):
{
"shoulder_pan": { "id": 1, "drive_mode": 0, "homing_offset": 56, "range_min": 829, "range_max": 2866 },
"shoulder_lift": { "id": 2, "drive_mode": 0, "homing_offset": -1024, "range_min": 1024, "range_max": 3071 },
"elbow_flex": { "id": 3, "drive_mode": 0, "homing_offset": 0, "range_min": 2048, "range_max": 3071 },
"wrist_flex": { "id": 4, "drive_mode": 0, "homing_offset": 0, "range_min": 1024, "range_max": 3071 },
"wrist_roll": { "id": 5, "drive_mode": 0, "homing_offset": 0, "range_min": 0, "range_max": 4095 },
"gripper": { "id": 6, "drive_mode": 0, "homing_offset": 0, "range_min": 2048, "range_max": 3071 }
}| 字段 | 含义 | 数值不对会怎样 |
|---|---|---|
id | 舵机在总线上的编号 1–6 | 编号与关节错位 → 主臂推 J1、从臂动的是别的关节 |
drive_mode | 驱动方向,0 正常、1 取反;用于该关节装反了的场合 | 方向没取反 → 该关节朝反方向顶限位(正是第 1 篇说的烧舵机场景) |
homing_offset | 物理零位到编码器零点的偏移,校准第一轮「摆中位」测的就是它 | 偏了 → 所有关节角度整体带固定偏差,遥操作时从臂姿态与主臂对不上 |
range_min / range_max | 软件限位,来自校准第二轮「走满行程」的两个端点 | 范围偏小 → 动作被截断;MIN == MAX → 说明那一轮根本没推动这个关节,必须重校准 |
这两个数组值同时会被写进舵机自己的 Min/Max_Position_Limit 寄存器,所以校准文件与舵机内部状态是成对的:换了一份 .json 却还是同一台臂,软件限位就可能和实际行程对不上。这也是为什么「换电脑直接拷 JSON」可行、但「把别人的 JSON 拷给自己」不行。
4.7 遥操作
两臂都就绪后,主臂的动作才会带动从臂。
lerobot-teleoperate \
--robot.type=so101_follower \
--robot.port=/dev/tty.usbmodem5B8E1136841 \
--robot.id=my_awesome_follower_arm \
--teleop.type=so101_leader \
--teleop.port=/dev/tty.usbmodem5B790188161 \
--teleop.id=my_awesome_leader_arm--robot.id / --teleop.id 在校准和遥操作两步里必须写同一个名字,否则找不到对应的校准文件。从臂就绪后,裸命令即可连上:
my_awesome_leader_arm SO101Leader connected.
my_awesome_follower_arm SO101Follower connected.
Teleop loop time: 16.67ms (60 Hz)